﻿/*
 * KLF Headless Browser
 * Written by Kris Oye <kristianoye@gmail.com>
 * Copyright (C) 2011-2012.  All rights reserved.
 */
namespace KLF.Lib.Browser.JavaScriptHelpers
{
    #region Using Statements

    using System;
    using System.Linq;
    using System.Threading;
    using KLF.Lib.Browser.DOM;
    using KLF.Lib.Browser.DOM.HTML;
    using System.Collections.Generic;
    using Jint.Native;
    using KLF.Lib.Browser.JavascriptHelpers;

    #endregion

    /// <summary>
    /// Provides a Javascript-compliant interface to the internal
    /// underlying window object.
    /// </summary>
    public class WindowHelper
    {
        #region Constructors

        public WindowHelper(RequestContext _context, Document _doc)
        {
            _History = new History(_context.Window);
            _Timers = new Dictionary<int, Timer>();
            _Window = _context.Window;
            _Context = _context;
            _document = _doc;
        }

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        protected RequestContext _Context { get; set; }

        /// <summary>
        /// Encapsulates the browser's history in a sciprtable representation.
        /// </summary>
        protected History _History { get; set; }

        protected Dictionary<int,Timer> _Timers { get; set; }

        /// <summary>
        /// The underlying browser window.
        /// </summary>
        protected BrowserWindow _Window { get; set; }

        #endregion

        #region JavasScript window Methods and Properties

        /// <summary>
        /// Simulates an alert() by writing to the console.
        /// </summary>
        /// <param name="str"></param>
        public void alert(JsObject str)
        {
            if (str.Value is String)
            {
                Console.WriteLine("window.alert({0})", str.Value.ToString());
            }
        }

        /// <summary>
        /// Simulates a blur() call
        /// </summary>
        public void blur()
        {
            Console.WriteLine("window.blur()");
        }

        /// <summary>
        /// The clearInterval() method clears a timer set with the 
        /// setInterval() method.
        /// </summary>
        public void clearInterval(int timerId)
        {
            if (_Timers.ContainsKey(timerId))
            {
                _Timers[timerId].Dispose();
                _Timers.Remove(timerId);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="timerId"></param>
        public void clearTimeout(int timerId)
        {
            clearInterval(timerId);
        }

        /// <summary>
        /// Close the window.
        /// </summary>
        public void close()
        {
            Console.WriteLine("window.close()");
            closed = true;
        }

        /// <summary>
        /// Returns true if the window has been closed, false if it is open.
        /// </summary>
        public bool closed { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool confirm(string msg)
        {
            //  TODO: Add some event handlers for this and prompt()
            Console.WriteLine("window.confirm({0})", msg);
            return false;
        }

        /// <summary>
        /// IE-only method
        /// </summary>
        public void createPopup()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Status isn't supported much, but heh you can set it anyway.
        /// </summary>
        public string defaultStatus { get; set; }

        /// <summary>
        /// Escapes a string like only JavaScript can... blech.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public string escape(string s)
        {
            return Microsoft.JScript.GlobalObject.escape(s);
        }

        /// <summary>
        /// Periodically execute an interval or timeout event.
        /// </summary>
        /// <param name="state"></param>
        internal void executeTimer(object state)
        {
            WindowTimerEvent timerState = state as WindowTimerEvent;

            if (state != null)
            {
                if (!String.IsNullOrEmpty(timerState.TimerCode))
                    _Context.Script.Run(timerState.TimerCode);
                else
                    timerState.IsTimeout = true;

                if (_Timers.ContainsKey(timerState.TimerID))
                {
                    if (timerState.IsTimeout)
                    {
                        _Timers[timerState.TimerID].Dispose();
                        _Timers.Remove(timerState.TimerID);
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void focus()
        {
            // TODO: Set window as active window in browser?
        }

        /// <summary>
        /// Returns a collection of frames and iframes from current document.
        /// </summary>
        public Element[] frames
        {
            get
            {
                return _document.Elements
                    .Where(e =>
                        e.tagName.Equals(HTMLConstants.HTML_TAG_FRAME) ||
                        e.tagName.Equals(HTMLConstants.HTML_TAG_IFRAME))
                    .ToArray();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public History history { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public int innerHeight { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public int innerWidth { get; set; }

        public IStorage<string> localStorage
        {
            get
            {
                if (_storage == null)
                    _storage = new Storage<string>();
                return _storage;
            }
        }
        internal Storage<string> _storage { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public Location location
        {
            get
            {
                return new Location(_Context.Uri);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void moveBy(int x, int y)
        {
            screenX += x;
            screenY += y;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void moveTo(int x, int y)
        {
            screenX = x;
            screenY = y;
        }

        /// <summary>
        /// 
        /// </summary>
        public UserAgent navigator
        {
            get
            {
                return _Context.Browser.Options.Agent;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="name"></param>
        /// <param name="specs"></param>
        /// <param name="replace"></param>
        public void open(string url, string name, string specs, bool replace)
        {
            Console.WriteLine("window.open({0}, {1}, {2})", url, name, specs);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="name"></param>
        /// <param name="specs"></param>
        public void open(string url, string name, string specs)
        {
            open(url, name, specs, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="name"></param>
        public void open(string url, string name)
        {
            open(url, name, "", true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        public void open(string url)
        {
            open(url, "", "", true);
        }

        /// <summary>
        /// Returns a reference to the window that created the window.
        /// </summary>
        public WindowHelper opener
        {
            get;
            internal set;
        }

        /// <summary>
        /// 
        /// </summary>
        public int outerHeight { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public int outerWidth { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public int pageXOffset { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public int pageYOffset { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public WindowHelper parent { get; internal set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="defaultText"></param>
        /// <returns></returns>
        public string prompt(string msg, string defaultText)
        {
            Console.Write(String.Format("\nwindow.prompt({0}): ", msg));

            return defaultText ?? "";
        }

        /// <summary>
        /// The resizeBy() method resizes a window by the specified amount.
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void resizeBy(int width, int height)
        {
            outerHeight += height;
            outerWidth += width;
        }

        /// <summary>
        /// Resizes the window to the specified width and height
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void resizeTo(int width, int height)
        {
            outerHeight = height;
            outerWidth = width;
        }

        /// <summary>
        /// 
        /// </summary>
        public int screenLeft
        {
            get { return screenX; }
            set { screenX = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public int screenTop
        {
            get { return screenY; }
            set { screenY = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public int screenX { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public int screenY { get; set; }

        /// <summary>
        /// Not sure if this exists.
        /// </summary>
        public void scroll()
        {
        }

        /// <summary>
        /// The scrollBy() method scrolls the content by the specified number of pixels.
        /// </summary>
        /// <param name="xnum"></param>
        /// <param name="ynum"></param>
        public void scrollBy(int xnum, int ynum)
        {
            pageXOffset += xnum;
            pageYOffset += ynum;
        }

        /// <summary>
        /// The scrollTo() method scrolls the content to the specified coordinates.
        /// </summary>
        /// <param name="xpos"></param>
        /// <param name="ypos"></param>
        public void scrollTo(int xpos, int ypos)
        {
            pageXOffset = xpos;
            pageYOffset = ypos;
        }

        /// <summary>
        /// 
        /// </summary>
        public WindowHelper self { get { return this; } }

        public IStorage<Storage<string>> sessionStorage
        {
            get
            {
                if (_sessionStorage == null)
                    _sessionStorage = new Storage<Storage<string>>();

                return _sessionStorage;
            }
        }
        internal Storage<Storage<string>> _sessionStorage { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        public int setInterval(string code, int interval)
        {
            int timerId = new Random().Next();

            while (_Timers.ContainsKey(timerId))
            {
                timerId = new Random().Next();
            }
            Timer t = new Timer(new TimerCallback(executeTimer), 
                new WindowTimerEvent  { 
                    TimerCode = code,
                    TimerID = timerId, 
                    IsTimeout = false }, 0, interval);

            return timerId;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="code"></param>
        /// <param name="timeoutPeriod"></param>
        /// <returns></returns>
        public int setTimeout(string code, int timeoutPeriod)
        {
            int timerId = new Random().Next();

            lock (_Timers)
            {
                while (_Timers.ContainsKey(timerId))
                {
                    timerId = new Random().Next();
                }
                Timer t = new Timer(new TimerCallback(executeTimer),
                    new WindowTimerEvent
                    {
                        TimerCode = code,
                        TimerID = timerId,
                        IsTimeout = true
                    }, timeoutPeriod, Timeout.Infinite);
                _Timers[timerId] = t;
            }

            return timerId;
        }

        /// <summary>
        /// 
        /// </summary>
        public string status
        {
            get { return defaultStatus; }
            set { defaultStatus = value; }
        }

        /// <summary>
        /// Returns the topmost browser window.
        /// </summary>
        public WindowHelper top
        {
            get
            {
                WindowHelper t = this;

                do
                {
                    if (t.parent != null)
                        t = t.parent;
                }
                while (t.parent != null);

                return t;
            }
        }

        public Document _document
        {
            get;
            set;
        }

        /// <summary>
        /// Unescape a Javascript escaped string.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public string unescape(string s)
        {
            return Microsoft.JScript.GlobalObject.unescape(s);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="str"></param>
        public void write(string str)
        {
            HTMLDocument doc = _Window.ActiveDocument as HTMLDocument;

            if (doc != null)
            {
                doc.write(str.ToString());
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="str"></param>
        public void writeln(string str)
        {
            HTMLDocument doc = _Window.ActiveDocument as HTMLDocument;

            if (doc != null)
            {
                doc.writeln(str.ToString());
            }
        }

        #endregion
    }
}
